#include "src/pbc/portable-constant-array-builder.h"

#include <cmath>

#include "src/pbc/portable-function.h"
#include "src/pbc/portable-literals.h"
#include "src/pbc/portable-scopes.h"

namespace portable {

ConstantArrayBuilder::ConstantArraySlice::ConstantArraySlice(
    size_t start_index, size_t capacity, OperandSize operand_size)
    : start_index_(start_index),
      capacity_(capacity),
      reserved_(0),
      operand_size_(operand_size),
      constants_() {}

void ConstantArrayBuilder::ConstantArraySlice::Reserve() {
  P_DCHECK_GT(available(), 0u);
  reserved_++;
  P_DCHECK_LE(reserved_, capacity() - constants_.size());
}

void ConstantArrayBuilder::ConstantArraySlice::Unreserve() {
  P_DCHECK_GT(reserved_, 0u);
  reserved_--;
}

size_t ConstantArrayBuilder::ConstantArraySlice::Allocate(
    ConstantArrayBuilder::Entry entry, size_t count) {
  P_DCHECK_GE(available(), count);
  size_t index = constants_.size();
  P_DCHECK_LT(index, capacity());
  for (size_t i = 0; i < count; ++i) {
    constants_.push_back(entry);
  }
  return index + start_index();
}

ConstantArrayBuilder::Entry& ConstantArrayBuilder::ConstantArraySlice::At(
    size_t index) {
  P_DCHECK_GE(index, start_index());
  P_DCHECK_LT(index, start_index() + size());
  return constants_[index - start_index()];
}

const ConstantArrayBuilder::Entry& ConstantArrayBuilder::ConstantArraySlice::At(
    size_t index) const {
  P_DCHECK_GE(index, start_index());
  P_DCHECK_LT(index, start_index() + size());
  return constants_[index - start_index()];
}

const size_t ConstantArrayBuilder::k8BitCapacity;
const size_t ConstantArrayBuilder::k16BitCapacity;
const size_t ConstantArrayBuilder::k32BitCapacity;

ConstantArrayBuilder::ConstantArrayBuilder()
    : bigints_map_(),
      scopes_map_(),
      strings_map_(),
      smi_map_(),
      smi_pairs_(),
      heap_number_map_() {
  idx_slice_[0] = new ConstantArraySlice(0, k8BitCapacity, OperandSize::kByte);
  idx_slice_[1] = new ConstantArraySlice(k8BitCapacity, k16BitCapacity,
                                         OperandSize::kShort);
  idx_slice_[2] = new ConstantArraySlice(k8BitCapacity + k16BitCapacity,
                                         k32BitCapacity, OperandSize::kQuad);
}

ConstantArrayBuilder::~ConstantArrayBuilder() {
  for (auto val : idx_slice_) {
    if (val) delete val;
  }
}

size_t ConstantArrayBuilder::size() const {
  size_t i = arraysize(idx_slice_);
  while (i > 0) {
    ConstantArraySlice* slice = idx_slice_[--i];
    if (slice->size() > 0) {
      return slice->start_index() + slice->size();
    }
  }
  return idx_slice_[0]->size();
}

ConstantArrayBuilder::ConstantArraySlice* ConstantArrayBuilder::IndexToSlice(
    size_t index) const {
  for (ConstantArraySlice* slice : idx_slice_) {
    if (index <= slice->max_index()) {
      return slice;
    }
  }
  P_UNREACHABLE();
}

ConstantPool* ConstantArrayBuilder::ToConstantPool() {
  ConstantPool* pool = new ConstantPool();
  pool->Resize(size());
  uint32_t array_index = 0;
  for (const ConstantArraySlice* slice : idx_slice_) {
    P_DCHECK_EQ(slice->reserved(), static_cast<size_t>(0));
    P_DCHECK(array_index == 0 ||
             IsPowerOfTwo(static_cast<uint32_t>(array_index)));
    // Copy objects from slice into array.
    for (size_t i = 0; i < slice->size(); ++i) {
      Entry entry = slice->At(slice->start_index() + i);
      pool->Set(array_index++, entry);
    }
    // Leave holes where reservations led to unused slots.
    size_t padding = slice->capacity() - slice->size();
    if (static_cast<size_t>(pool->length() - array_index) <= padding) {
      break;
    }
    array_index += padding;
  }
  P_DCHECK_GE(array_index, pool->length());
  return pool;
}

size_t ConstantArrayBuilder::Insert(Smi smi) {
  auto entry = smi_map_.find(smi);
  if (entry == smi_map_.end()) {
    return AllocateReservedEntry(smi);
  }
  return entry->second;
}

size_t ConstantArrayBuilder::Insert(double number) {
  if (std::isnan(number)) return InsertNaN();
  auto entry = heap_number_map_.find(number);
  if (entry == heap_number_map_.end()) {
    index_t index = static_cast<index_t>(AllocateIndex(Entry(number)));
    heap_number_map_[number] = index;
    return index;
  }
  return entry->second;
}

size_t ConstantArrayBuilder::Insert(String str) {
  auto found = strings_map_.find(str);
  if (found != strings_map_.end()) {
    return found->second;
  }
  auto index = AllocateIndex(Entry(str));
  strings_map_.emplace(str, index);
  return index;
}

size_t ConstantArrayBuilder::Insert(PortableBigInt bigint) {
  auto found = bigints_map_.find(bigint.str());
  if (found != bigints_map_.end()) {
    return found->second;
  }
  auto index = AllocateIndex(Entry(bigint));
  bigints_map_.emplace(bigint.str(), index);
  return index;
}

size_t ConstantArrayBuilder::Insert(const Scope* scope) {
  auto found = scopes_map_.find(reinterpret_cast<intptr_t>(scope));
  if (found != scopes_map_.end()) {
    return found->second;
  }
  auto index = AllocateIndex(Entry(scope));
  scopes_map_.emplace(reinterpret_cast<intptr_t>(scope), index);
  return index;
}

size_t ConstantArrayBuilder::Insert(LiteralElement* literal) {
  auto index = AllocateIndex(Entry(literal));
  return index;
}

size_t ConstantArrayBuilder::Insert(TemplateObjectDescription* literal) {
  auto index = AllocateIndex(Entry(literal));
  return index;
}

#define INSERT_ENTRY(NAME, LOWER_NAME)              \
  size_t ConstantArrayBuilder::Insert##NAME() {     \
    if (LOWER_NAME##_ < 0) {                        \
      LOWER_NAME##_ = AllocateIndex(Entry::NAME()); \
    }                                               \
    return LOWER_NAME##_;                           \
  }
SINGLETON_CONSTANT_ENTRY_TYPES(INSERT_ENTRY)
#undef INSERT_ENTRY

ConstantArrayBuilder::index_t ConstantArrayBuilder::AllocateIndex(
    ConstantArrayBuilder::Entry entry) {
  return AllocateIndexArray(entry, 1);
}

ConstantArrayBuilder::index_t ConstantArrayBuilder::AllocateIndexArray(
    ConstantArrayBuilder::Entry entry, size_t count) {
  for (size_t i = 0; i < arraysize(idx_slice_); ++i) {
    if (idx_slice_[i]->available() >= count) {
      return static_cast<index_t>(idx_slice_[i]->Allocate(entry, count));
    }
  }
  P_UNREACHABLE();
}

ConstantArrayBuilder::ConstantArraySlice*
ConstantArrayBuilder::OperandSizeToSlice(OperandSize operand_size) const {
  ConstantArraySlice* slice = nullptr;
  switch (operand_size) {
    case OperandSize::kNone:
      P_UNREACHABLE();
    case OperandSize::kByte:
      slice = idx_slice_[0];
      break;
    case OperandSize::kShort:
      slice = idx_slice_[1];
      break;
    case OperandSize::kQuad:
      slice = idx_slice_[2];
      break;
  }
  P_DCHECK(slice->operand_size() == operand_size);
  return slice;
}

size_t ConstantArrayBuilder::InsertDeferred() {
  return AllocateIndex(Entry::Deferred());
}

size_t ConstantArrayBuilder::InsertJumpTable(size_t size) {
  return AllocateIndexArray(Entry::UninitializedJumpTableSmi(), size);
}

void ConstantArrayBuilder::SetDeferredAt(size_t index, Function* func) {
  ConstantArraySlice* slice = IndexToSlice(index);
  return slice->At(index).SetDeferred(func);
}

void ConstantArrayBuilder::SetDeferredAt(size_t index,
                                         std::vector<String>* declarations) {
  ConstantArraySlice* slice = IndexToSlice(index);
  return slice->At(index).SetDeferred(declarations);
}

void ConstantArrayBuilder::SetJumpTableSmi(size_t index, Smi smi) {
  ConstantArraySlice* slice = IndexToSlice(index);
  // Allow others to reuse these Smis, but insert using emplace to avoid
  // overwriting existing values in the Smi map (which may have a smaller
  // operand size).
  smi_map_.emplace(smi, static_cast<index_t>(index));
  return slice->At(index).SetJumpTableSmi(smi);
}

OperandSize ConstantArrayBuilder::CreateReservedEntry(
    OperandSize minimum_operand_size) {
  for (size_t i = 0; i < arraysize(idx_slice_); ++i) {
    if (idx_slice_[i]->available() > 0 &&
        idx_slice_[i]->operand_size() >= minimum_operand_size) {
      idx_slice_[i]->Reserve();
      return idx_slice_[i]->operand_size();
    }
  }
  P_UNREACHABLE();
}

ConstantArrayBuilder::index_t ConstantArrayBuilder::AllocateReservedEntry(
    Smi value) {
  index_t index = static_cast<index_t>(AllocateIndex(Entry(value)));
  smi_map_[value] = index;
  return index;
}

size_t ConstantArrayBuilder::CommitReservedEntry(OperandSize operand_size,
                                                 Smi value) {
  DiscardReservedEntry(operand_size);
  size_t index;
  auto entry = smi_map_.find(value);
  if (entry == smi_map_.end()) {
    index = AllocateReservedEntry(value);
  } else {
    ConstantArraySlice* slice = OperandSizeToSlice(operand_size);
    index = entry->second;
    if (index > slice->max_index()) {
      // The object is already in the constant array, but may have an
      // index too big for the reserved operand_size. So, duplicate
      // entry with the smaller operand size.
      index = AllocateReservedEntry(value);
    }
    P_DCHECK_LE(index, slice->max_index());
  }
  return index;
}

void ConstantArrayBuilder::DiscardReservedEntry(OperandSize operand_size) {
  OperandSizeToSlice(operand_size)->Unreserve();
}

ConstantPool::~ConstantPool() {
  for (auto entry : entries_) {
    switch (entry.tag_) {
      case Tag::kArrayLiteral:
      case Tag::kObjectLiteral:
      case Tag::kClassLiteral:
        delete reinterpret_cast<LiteralElement*>(entry.ref_);
        break;
      case Tag::kTopDeclarations:
        delete reinterpret_cast<std::vector<String>*>(entry.ref_);
        break;
      case Tag::kTemplateObjectLiteral:
        delete reinterpret_cast<TemplateObjectDescription*>(entry.ref_);
        break;
      default:
        break;
    }
  }
}

void ConstantPool::Print(std::ostream& os) {
  int index = 0;
  for (auto entry : entries_) {
    os << "         ";
    os << index << ": ";
    switch (entry.tag_) {
      case Tag::kSmi: {
        os << "<Smi: " << static_cast<int32_t>(entry.ref_) << ">" << std::endl;
        break;
      }
      case Tag::kU8String: {
        auto str = std::get_if<std::string>(&entry.str_);
        os << "<U8String[" << str->length() << "]: " << *str << ">"
           << std::endl;
        break;
      }
      case Tag::kU16String: {
        auto str = std::get_if<std::u16string>(&entry.str_);
        os << "<U16String[" << str->length() << "]: u#";
        for (char16_t c : *str) {
          os << "\\x" << std::setw(4) << std::setfill('0') << std::hex
             << static_cast<int32_t>(c);
        }
        os << ">" << std::endl;
        break;
      }
      case Tag::kNumber: {
        os << "<Number: " << *reinterpret_cast<double*>(&entry.ref_) << ">"
           << std::endl;
        break;
      }
      case Tag::kBigInt: {
        os << "<BigInt: " << entry.bigint_.str() << ">" << std::endl;
        break;
      }
      case Tag::kScope: {
        os << "<Scope: ";
        auto scope = reinterpret_cast<Scope*>(entry.ref_);
        os << scope << " ";
        auto name = scope->function_name();
        auto* scopeTypeName = scope->getScopeTypeName();
        size_t numLocals = scope->GetLocals().size();
        if (auto str = std::get_if<std::string>(&name)) {
          os << *str << "[" << scopeTypeName << "](num_slots=" << numLocals
             << ")>" << std::endl;
        } else {
          auto u16str = std::get_if<std::u16string>(&name);
          for (char16_t c : *u16str) {
            os << "\\x" << std::setw(4) << std::setfill('0') << std::hex
               << static_cast<int32_t>(c);
          }
          os << "[" << scopeTypeName << "](num_slots=" << numLocals << ")>"
             << std::endl;
        }
        break;
      }
      case Tag::kFunction: {
        os << "<Function: ";
        auto function = reinterpret_cast<Function*>(entry.ref_);
        auto name = function->name();
        if (auto str = std::get_if<std::string>(&name)) {
          os << *str << ">" << std::endl;
        } else {
          auto u16str = std::get_if<std::u16string>(&name);
          for (char16_t c : *u16str) {
            os << "\\x" << std::setw(4) << std::setfill('0') << std::hex
               << static_cast<int32_t>(c);
          }
          os << ">" << std::endl;
        }
        break;
      }
      case Tag::kTopDeclarations: {
        os << "<TopLevelDeclarations>" << std::endl;
        break;
      }
      case Tag::kArrayLiteral: {
        os << "<ArrayLiteral>" << std::endl;
        break;
      }
      case Tag::kObjectLiteral: {
        os << "<ObjectLiteral>" << std::endl;
        break;
      }
      case Tag::kTemplateObjectLiteral: {
        os << "<TemplateObjectLiteral>" << std::endl;
        break;
      }
      case Tag::kClassLiteral: {
        os << "<ClassLiteral>" << std::endl;
        break;
      }
      case Tag::kUninitializedJumpTableSmi: {
        os << "<UninitializedJumpTableSmi>" << std::endl;
        break;
      }
      case Tag::kJumpTableSmi: {
        os << "<JumpTableSmi>: " << static_cast<int32_t>(entry.ref_) << ">"
           << std::endl;
        break;
      }
      case Tag::kTheHoleValue: {
        os << "<TheHoleValue>" << std::endl;
        break;
      }
#define SINGLETON_ENTRY(NAME, LOWER_NAME)         \
  case Tag::k##NAME: {                            \
    os << "<" << #LOWER_NAME << ">" << std::endl; \
    break;                                        \
  }
        SINGLETON_CONSTANT_ENTRY_TYPES(SINGLETON_ENTRY)
#undef SINGLETON_ENTRY
      default:
        P_UNREACHABLE();
    }
    ++index;
  }
}

}  // namespace portable
